/*
 * Copyright (c) 2023 fred
 * Licensed under the Apache License,Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
import { Evaluator } from './Evaluator';
import List from '@ohos.util.List';
import { Element } from '../nodes/Element';
import { StringUtil } from '../internal/StringUtil';
import logUtil from '../util/LogUtil';
import { EvaluatorTag } from './Evaluator/EvaluatorTag';

export abstract class CombiningEvaluator extends Evaluator {
  // maintain original order so that #toString() is sensible
  readonly evaluators: List<Evaluator>
  // cost ascending order
  readonly sortedEvaluators: List<Evaluator>
  num = 0
  _cost = 0

  constructor();

  constructor(evaluators: List<Evaluator>)

  constructor(evaluators: Evaluator[])

  constructor(evaluators?: List<Evaluator> | Evaluator[]) {
    super()
    this.evaluators = new List()
    this.sortedEvaluators = new List()
    if (evaluators !== undefined && evaluators !== null) {
      logUtil.i("fredzz",`CombiningEvaluator ${evaluators.length}`)
      evaluators.forEach((value, index) => {
        this.evaluators.add(value)
      })
      this.updateEvaluators()
    }
  }

  reset() {
    for (let evaluator of this.evaluators) {
      evaluator.reset()
    }
    super.reset()
  }

  getCost(): number {
    return this._cost
  }

  rightMostEvaluator(): Evaluator {
    return this.num > 0 ? this.evaluators.get(this.num - 1) : null
  }

  replaceRightMostEvaluator(replacement: Evaluator) {
    this.evaluators.set(this.num - 1, replacement)
    this.updateEvaluators()
  }

  updateEvaluators() {
    // used so we don't need to bash on size() for every match test
    this.num = this.evaluators.length
    logUtil.i("fredzz","updateEvaluators:"+this.num)
    // sort the evaluators by lowest cost first, to optimize the evaluation order
    this._cost = 0;
    for (let evaluator of this.evaluators) {
      this._cost += evaluator.cost()
    }
    this.sortedEvaluators.clear();
    this.evaluators.forEach((value, index) => {
      this.sortedEvaluators.add(value)
    })
    this.sortedEvaluators.sort(function (a, b) {
      return a.cost() - b.cost()
    })
  }
}

export class CombiningEvaluatorAnd extends CombiningEvaluator {

  constructor(...evaluators: Evaluator[]) {
    super(evaluators)
  }

  public matches(root: Element, element: Element): boolean {
    logUtil.i("fredzz",`And :${this.num}`)
    for (let index = 0; index < this.num; index++) {
      let s = this.sortedEvaluators.get(index)
      if (s instanceof EvaluatorTag) {
        logUtil.i("fredzz","And matches:"+s.toString())
      } else {
        logUtil.i("fredzz","And matches:"+s)
      }

      if (!s.matches(root, element)) {
        return false
      }
    }
    return true
  }

  public toString(): string {
    return StringUtil.join(this.evaluators, "")
  }
}

export class CombiningEvaluatorOr extends CombiningEvaluator {

  constructor();
  constructor(...evaluators: Evaluator[]);

  constructor(...evaluators: Evaluator[]) {
    super(evaluators)
  }

  public add(e: Evaluator) {
    this.evaluators.add(e)
    this.updateEvaluators()
  }

  public matches(root: Element, element: Element): boolean {
    for (let index = 0; index < this.num; index++) {
      let s = this.sortedEvaluators.get(index)
      if (s.matches(root, element)) {
        return true
      }
    }
    return false
  }

  public toString():string {
    return StringUtil.join(this.evaluators,"")
  }


}